/*
 * Tf: a unit test framework for C
 * Copyright (C) 2012 zrezek at gmail.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#if !defined TF_INCLUDED
#define TF_INCLUDED

#include <math.h>
#include <string.h>

#ifdef _MSC_VER

#pragma section(".CRT$XCU",read)
#define TF_INITIALIZER(f) \
   static void __cdecl f(void); \
   __declspec(allocate(".CRT$XCU")) void (__cdecl*f##_)(void) = f; \
   static void __cdecl f(void)

#elif defined(__GNUC__)

#define TF_INITIALIZER(f) \
   static void f(void) __attribute__((constructor));
   static void f(void)

#endif

//! Doubly linked list node.
struct tf_node
{
    struct tf_node *next;
    struct tf_node *pred;
};

//! Double linked list.
struct tf_list
{
    struct tf_node *first;
    struct tf_node *dummy;
    struct tf_node *last;
};

struct tf_test
{
    struct tf_node node;
    
    const char* name;
    int flags;
    void (*test_function)();    
};

#define TF_FLAG_ADDED               0x1
#define TF_FLAG_ADDED_MULTIPLE      0x2

#define TF_INFINITY                 0x0

struct tf_fixture
{
    struct tf_node node;
    struct tf_list tests;

    int timeout;

    const char* path_and_name;

    void (*setup_function)();
    void (*teardown_function)();

    void (*init)(struct tf_fixture* fixture);
};

#define TF_BEGIN_TEST(name)                                                     \
            static void test_function##name();                                  \
                                                                                \
            static struct tf_test tf_test##name = {                             \
                    { 0, 0 },                                                   \
                    #name,                                                      \
                    0,                                                          \
                    test_function##name                                         \
            };                                                                  \
                                                                                \
            static void test_function##name()

#define TF_END_TEST

#define TF_BEGIN_FIXTURE(path, name)                                            \
            static void tf_fixture_init##name(struct tf_fixture* fixture);      \
                                                                                \
            static struct tf_fixture tf_fixture##name = {                       \
                    { 0, 0 },                                                   \
                    { 0, 0, 0 },                                                \
                    TF_INFINITY,                                                \
                    #path#name,                                                 \
                    0,                                                          \
                    0,                                                          \
                    tf_fixture_init##name                                       \
            };                                                                  \
                                                                                \
            TF_INITIALIZER(tf_fixture_global_initializer##name)                 \
            {                                                                   \
                tf_register_fixture(&tf_fixture##name);                         \
            }                                                                   \
                                                                                \
            static void tf_fixture_init##name (struct tf_fixture* fixture)

#define TF_END_FIXTURE

#define TF_TEST(name)                                                           \
            tf_add_test_item(fixture, &tf_test##name);

#define TF_SETUP(function)                                                      \
            tf_set_setup_func(fixture, function);

#define TF_TEARDOWN(function)                                                   \
            tf_set_teardown_func(fixture, function);

#define TF_TIMEOUT(timeout)                                                     \
            tf_set_fixture_timeout(fixture, timeout);

void tf_register_fixture(struct tf_fixture* fixture);
void tf_add_test_item(struct tf_fixture* fixture, struct tf_test* item);
void tf_set_setup_func(struct tf_fixture* fixture, void (*func)());
void tf_set_teardown_func(struct tf_fixture* fixture, void (*func)());
void tf_require(int condition_value, const char* condition_txt, const char* file, int line);
void tf_set_fixture_timeout(struct tf_fixture* fixture, int timeout);

//
#define TF_FAIL() \
            tf_require(0, 0, __FILE__, __LINE__)

#define TF_REQUIRE(expression) \
            tf_require((expression) != 0, #expression, __FILE__, __LINE__)

#define TF_REQUIRE_EQ(exp1, exp2) \
            tf_require((exp1) == (exp2), #exp1 " == " #exp2, __FILE__, __LINE__)

#define TF_REQUIRE_NEQ(exp1, exp2) \
            tf_require((exp1) != (exp2), #exp1 " != " #exp2, __FILE__, __LINE__)

#define TF_REQUIRE_CLOSE(exp1, exp2, tolerance) \
            tf_require(fabs((exp1) - (exp2)) < (tolerance) , #exp1 " ~= " #exp2, __FILE__, __LINE__)

#define TF_REQUIRE_STR_EQ(str1, str2) \
            tf_require(!strcmp(str1, str2), #str1 " == " #str2, __FILE__, __LINE__)

#endif // TF_INCLUDED
